package com.pig4cloud.pig.common.swagger.plugins;



import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Splitter;
import com.pig4cloud.pig.common.swagger.annotation.ApiGlobalModel;
import io.micrometer.core.instrument.util.StringUtils;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.MemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.QualifiedModelName;
import springfox.documentation.service.ParameterType;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 *
 * 参考文档：http://springfox.github.io/springfox/docs/current/#_example_apilistingscannerplugin
 *  使用统一的入参bean,注解生成特定的入参文档
 *  例如：
 *   @ApiGlobalModel(component = GlobalVo.class, value = "code,aliCode,otherType")
 * <p>

 */
@Slf4j
@Component
@Order
public class ApiGlobalModelBuilder extends ParameterBuilderComm implements ParameterBuilderPlugin {

    @Autowired
    private TypeResolver typeResolver;

    @Override
    public void apply(ParameterContext context) {
        try {
            // 从方法或参数上获取指定注解的Optional
            Optional<ApiGlobalModel> optional = context.getOperationContext().findAnnotation(ApiGlobalModel.class);
            if (!optional.isPresent()) {
                optional = context.resolvedMethodParameter().findAnnotation(ApiGlobalModel.class);
            }
            if (optional.isPresent()) {
                String key =StrUtil.isEmpty(optional.get().modelName()) ? DEFAULT_CLASS_NAME + paramsIndex.incrementAndGet() : optional.get().modelName();
                ApiGlobalModel apiAnno = optional.get();
                try {
                    //类名重复将导致swagger识别不准确 主动触发异常
                    Class.forName(BASE_PACKAGE +"."+ key);
                } catch (ClassNotFoundException e) {
                    String[] fields = apiAnno.value();
                    String separator = apiAnno.separator();
                    ClassPool pool = ClassPool.getDefault();
                    CtClass ctClass = pool.makeClass(BASE_PACKAGE +"." + key);
                    ctClass.setModifiers(Modifier.ABSTRACT);
                    //处理 javassist.NotFoundException
                    pool.insertClassPath(new ClassClassPath(apiAnno.component()));
                    CtClass globalCtClass = pool.getCtClass(apiAnno.component().getName());

                    //从globalCtClass拷贝指定字段到动态创建的类中
                    for (String field : merge(fields, separator)) {
                        //若指定的字段不存在 throw NotFoundException
                        CtField ctField = globalCtClass.getDeclaredField(field);
                        CtField newCtField = new CtField(ctField, ctClass);
                        handleField(newCtField);
                        ctClass.addField(newCtField);
                    }
                    //向documentContext的Models中添加我们新生成的Class
                    ResolvedType resolvedType = typeResolver.resolve(ctClass.toClass());
                    context.getDocumentationContext().getAdditionalModels()
                            .add(resolvedType);

                    //修改model参数的ModelRef为我们动态生成的class(swagger2 3.0源码不支持，需要修改源码 ApiListingScanner，提取扫描之前，需要移动位置)
                    QualifiedModelName qualifiedModelName = new QualifiedModelName(BASE_PACKAGE,key);
                    context.requestParameterBuilder()
                            .in(ParameterType.BODY)
                            .name("params")
                            .query(q ->
                                    q.allowEmptyValue(false)
                                            .model(m->m.referenceModel(rm->rm.key(k->k.qualifiedModelName(qualifiedModelName))))
                            );
                }
            }
        } catch (Exception e) {
            log.error("@ApiGlobalModel Error", e);
        }
    }

    private void handleField(CtField field) {
        //防止private又没有getter
        field.setModifiers(Modifier.PUBLIC);
        //有name的把字段名改为name
        //因为JSON格式化的原因,ApiModelProperty的name属性无效 所以如果有name,直接更改字段名为name
        AnnotationsAttribute annos = ((AnnotationsAttribute) field.getFieldInfo().getAttribute("RuntimeVisibleAnnotations"));
        if (annos != null) {
            Annotation anno = annos.getAnnotation(ApiModelProperty.class.getTypeName());
            if (anno != null) {
                MemberValue name = anno.getMemberValue("name");
                if (name != null) {
                    //这里返回的name会以引号包裹
                    String fName = name.toString().replace("\"", "").trim();
                    if (fName.length() > 0) {
                        field.setName(fName);
                    }
                }
            }
        }

    }

    /**
     * 字符串列表 分隔符 合并
     * A{"a","b,c","d"} => B{"a","d","b","c"}
     *
     * @param arr arr
     * @return list
     */
    private List<String> merge(String[] arr, String separator) {
        List<String> tmp = new ArrayList<>();
        Arrays.stream(arr).forEach(s -> {
            if (s.contains(separator)) {
                tmp.addAll(Arrays.asList(s.split(separator)));
            } else {
                tmp.add(s);
            }
        });
        return tmp;
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

    /**
     * 根据propertys中的值动态生成含有Swagger注解的javaBeen
     * @param properties 多个字段，逗号分隔
     * @param name
     * @param origin
     * @param annoType
     * @param annotation
     * @return
     * @throws NotFoundException
     */
    private Class<?> createRefModel(String properties, String name, Class<?> origin, Integer annoType, String[] annotation) throws NotFoundException{
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = null;
        try {
            ctClass = pool.makeClass(origin.getPackage().getName()+"."+name);
            ctClass.stopPruning(true);
        } catch (Exception e) {
            ctClass = pool.get(origin.getPackage().getName()+"."+name);
        }
        try {
            Field[] fields = origin.getDeclaredFields();

            {
                List<Field> fieldAll = Arrays.asList(fields);
                List<String> dealPropertieNames = Splitter.on(",").splitToList(StrUtil.removeAll(properties, " "));//去掉空格并用逗号分割
                List<Field> dealFileds = fieldAll.stream().filter(
                        //如果annoType 为空，则
                        f -> annoType == 0 ? (!(dealPropertieNames.contains(f.getName()))) : dealPropertieNames.contains(f.getName())
                ).collect(Collectors.toList());
                return createCtFileds(dealFileds, ctClass, annotation);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }



    public Class<?> createCtFileds(List<Field> dealFileds, CtClass ctClass, String[] annotationNew) throws CannotCompileException, NotFoundException {
        Map<String, String> map = new HashMap<>();
        if(ArrayUtil.isNotEmpty(annotationNew)) {
            map = Arrays.asList(annotationNew).stream().map(elem -> elem.split("@")).collect(Collectors.toMap(e -> e[0], e -> e[1]));
        }
        for (Field field : dealFileds) {
            CtField ctField = new CtField(typeTry(field), field.getName(), ctClass);
            if(ctClass.isFrozen()){
                ctClass.defrost();
            }
            ctField.setModifiers(java.lang.reflect.Modifier.PUBLIC);
            Schema annotation = field.getAnnotation(Schema.class);
            String apiModelPropertyValue = map.containsKey(field.getName())?map.get(field.getName()): Optional.ofNullable(annotation).map(s -> s.description()).orElse("");
            if (StringUtils.isNotBlank(apiModelPropertyValue)) { //添加model属性说明
                ConstPool constPool = ctClass.getClassFile().getConstPool();
                AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                Annotation ann = new Annotation(Schema.class.getName(), constPool);
                ann.addMemberValue("value", new StringMemberValue(apiModelPropertyValue, constPool));
                attr.addAnnotation(ann);
                ctField.getFieldInfo().addAttribute(attr);
            }
            ctClass.addField(ctField);
        }
        return ctClass.toClass();
    }

}
